#include "HomogeneousMedium.h"

using namespace OBR;

std::shared_ptr<CudaProgram> HomogeneousMedium::program;
CudaBuffer HomogeneousMedium::sbt_data_payload;

HomogeneousMedium::HomogeneousMedium() : Medium("shaders/Medium/HomogeneousMedium.cu")
{
    this->program_desc.callables.entryFunctionNameDC = "__direct_callable__homogeneous_medium";
    this->program_desc.kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES;
    this->program_desc.flags = OPTIX_GEOMETRY_FLAG_NONE;
}

HomogeneousMedium::~HomogeneousMedium()
{
}

void HomogeneousMedium::uploadVirtualProgramPayload(
    std::vector<std::shared_ptr<VirtualProgramEntry>> instances)
{
    // collect data
    HomogeneousMediumData *data = new HomogeneousMediumData[instances.size()];
    for (size_t i = 0; i < instances.size(); i++)
    {
        auto med = std::dynamic_pointer_cast<HomogeneousMedium>(instances[i]);
        data[i] = med->data;
    }

    // set payload
    size_t payload_size = sizeof(HomogeneousMediumData) * instances.size();
    this->sbt_data_payload.resize(payload_size);
    this->sbt_data_payload.upload(data, payload_size);

    delete[] data;
}

void HomogeneousMedium::_buildFromConfig(const Json::Value &config)
{
    if (config["type"].asString().compare("homogeneous") != 0)
    {
        std::cerr << "wrong config value is sent to HomogeneousMedium::_buildFromConfig"
                  << std::endl;
        std::cerr << "got:" << config << std::endl;
        throw std::runtime_error("invalid parameter");
    }
    this->data.g = config["g"].asFloat();
    ConfigHelpers::loadSpectrum(config["sigma_s"], &this->data.sigma_s);
    bool has_sigma_t = config.isMember("sigma_t");
    bool has_sigma_a = config.isMember("sigma_a");
    if (has_sigma_t ^ has_sigma_a)
    {
        if (has_sigma_t)
        {
            ConfigHelpers::loadSpectrum(config["sigma_t"], &this->data.sigma_t);
            this->data.sigma_a = this->data.sigma_t - this->data.sigma_s;
        }
        else
        {
            ConfigHelpers::loadSpectrum(config["sigma_a"], &this->data.sigma_a);
            this->data.sigma_t = this->data.sigma_s + this->data.sigma_a;
        }
    }
    else
    {
        std::cerr << "invalid parameter, need to have exact one sigma_t / sigma_a" << std::endl;
        throw std::runtime_error("invalid parameter");
    }
}

CUdeviceptr HomogeneousMedium::getPayloadDevicePtr()
{
    return HomogeneousMedium::sbt_data_payload.d_pointer();
}

void HomogeneousMedium::setProgram(std::shared_ptr<CudaProgram> prog)
{
    HomogeneousMedium::program = prog;
}

std::shared_ptr<CudaProgram> HomogeneousMedium::getProgram()
{
    return HomogeneousMedium::program;
}

OptixProgramGroupOptions &HomogeneousMedium::getProgramGroupOptions()
{
    return this->program_opt;
}

OptixProgramGroupDesc &HomogeneousMedium::getProgramGroupDesc()
{
    this->program_desc.callables.moduleDC = this->cuda_module->optix_module;
    return this->program_desc;
}